package cn.bossfriday.im.common.api;

import cn.bossfriday.common.exception.ServiceRuntimeException;
import cn.bossfriday.common.http.IHttpProcessor;
import cn.bossfriday.common.router.ClusterRouterFactory;
import cn.bossfriday.common.rpc.actor.ActorRef;
import cn.bossfriday.common.rpc.actor.BaseUntypedActor;
import cn.bossfriday.common.utils.GsonUtil;
import cn.bossfriday.im.common.entity.result.ResultCode;
import cn.bossfriday.im.common.rpc.message.ApiMessage;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.StringUtils;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.Set;

import static cn.bossfriday.im.common.constant.ApiConstant.ATTRIBUTE_KEY_API_VERSION;
import static cn.bossfriday.im.common.constant.ApiConstant.ATTRIBUTE_KEY_APP_ID;

/**
 * BaseHttpProcessor
 *
 * @author chenx
 */
@Slf4j
public abstract class BaseHttpProcessor implements IHttpProcessor {

    public static final long API_RPC_TTL = 20000;

    private static final ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
    private static final Validator validator = factory.getValidator();

    @Override
    public void process(ChannelHandlerContext ctx, FullHttpRequest httpRequest) {
        try {
            String apiVersion = ctx.channel().attr(ATTRIBUTE_KEY_API_VERSION).get();
            long appId = ctx.channel().attr(ATTRIBUTE_KEY_APP_ID).get();
            this.doRequest(ctx, httpRequest, apiVersion, appId);
        } catch (Exception ex) {
            log.error("API process error!", ex);
            ApiResponseHelper.sendApiResponse(ctx, ResultCode.SYSTEM_ERROR);
        }
    }

    /**
     * doRequest
     *
     * @param ctx
     * @param httpRequest
     * @param apiVersion
     * @param appId
     */
    protected abstract void doRequest(ChannelHandlerContext ctx, FullHttpRequest httpRequest, String apiVersion, long appId);

    /**
     * getRequestPayload
     */
    protected <T> T getRequestPayload(FullHttpRequest httpRequest, Class<T> clazz) {
        try {
            String requestContent = this.getHttpRequestContent(httpRequest);
            if (StringUtils.isEmpty(requestContent)) {
                throw new ServiceRuntimeException("requestContent is empty!");
            }

            T request = GsonUtil.fromJson(requestContent, clazz);
            Set<ConstraintViolation<T>> violations = validator.validate(request);
            if (CollectionUtils.isEmpty(violations)) {
                return request;
            }

            if (!violations.isEmpty()) {
                StringBuilder errorMessage = new StringBuilder("Request Error: ");
                for (ConstraintViolation<T> violation : violations) {
                    errorMessage.append(violation.getPropertyPath())
                            .append(": ")
                            .append(violation.getMessage())
                            .append("\n");
                }

                throw new ServiceRuntimeException(errorMessage.toString());
            }

            return request;
        } catch (Exception ex) {
            throw new ServiceRuntimeException("getRequestPayload() error!");
        }
    }

    /**
     * getApiMessage
     *
     * @return
     */
    protected ApiMessage getApiMessage(String apiVersion, long appId, String method, String requesterId, String targetId, Object payload) {
        if (StringUtils.isEmpty(apiVersion)) {
            throw new ServiceRuntimeException("getApiMessage error: apiVersion is empty!");
        }

        if (StringUtils.isEmpty(method)) {
            throw new ServiceRuntimeException("getApiMessage error: method is empty!");
        }

        if (StringUtils.isEmpty(requesterId)) {
            throw new ServiceRuntimeException("getApiMessage error: requesterId is empty!");
        }

        if (StringUtils.isEmpty(targetId)) {
            throw new ServiceRuntimeException("getApiMessage error: targetId is empty!");
        }

        if (Objects.isNull(payload)) {
            throw new ServiceRuntimeException("getApiMessage error: payload is null!");
        }

        return ApiMessage.builder()
                .apiVersion(apiVersion)
                .appId(appId)
                .method(method)
                .targetId(targetId)
                .payload(payload)
                .build();
    }

    /**
     * getSender
     *
     * @param actorClass
     * @param ctx
     * @return
     */
    protected ActorRef getSender(Class<? extends BaseUntypedActor> actorClass, ChannelHandlerContext ctx) {
        return ClusterRouterFactory.getClusterRouter().getActorSystem().actorOf(API_RPC_TTL, actorClass, ctx);
    }

    /**
     * routeMessage
     */
    protected void routeMessage(ApiMessage msg) {
        ClusterRouterFactory.getClusterRouter().routeMessage(msg.getRoutableBean(), ActorRef.noSender());
    }

    /**
     * routeMessage - with sender & ttl
     */
    protected void routeMessage(ApiMessage msg, ActorRef sender) {
        ClusterRouterFactory.getClusterRouter().routeMessage(msg.getRoutableBean(), sender);
    }

    /**
     * getHttpRequestContent
     */
    private String getHttpRequestContent(FullHttpRequest httpRequest) {
        return httpRequest.content().toString(StandardCharsets.UTF_8);
    }
}
